package com.weimob.mcloud.wechatwork.customer.service.user.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author confool
 * @date 2019/3/1- 11:25
 * @company WeiMob
 * @description beanUtils 拷贝工具类
 */
@Slf4j
public class BeanUtil {

    /**
     * 静态化内存操作
     */
    private static final Map<String, PropertyDescriptor> cachePropertiesDescriptor;

    /**
     * 默认忽略的字段名称
     */
    private static final String defaultIgnoreFiledName = "class";


    static {
        cachePropertiesDescriptor = new HashMap<>();
    }


    /**
     * copy 对象
     *
     * @param source           原对象
     * @param target           目标对象
     * @param ignoreProperties 需要忽略的property名称
     */
    public static void copyProperties(Object source, Object target, String... ignoreProperties) {
        if (source == null) throw new RuntimeException("bean 拷贝 源对象不能为空");
        if (target == null) throw new RuntimeException("bean 拷贝 目标对象不能为空");
        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;
        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            LogUtils.error(BeanUtil.class, "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            throw new RuntimeException(ex);
                        }
                    }
                }
            }
        }
    }


    /**
     * bean list copy数据
     *
     * @param sourceList  原拷贝对象list
     * @param targetClass 目标对象字节码
     * @param <T>         泛型数据
     * @return 目标对象list
     */
    public static <T> List<T> copyPropertiesList(List<?> sourceList, Class<T> targetClass) {
        if (Utils.isBlank(sourceList)) return null;
        List<T> targetList = new ArrayList<>(sourceList.size());
        try {
            for (Object source : sourceList) {
                T target = targetClass.newInstance();
                copyProperties(source, target);
                targetList.add(target);
            }
        } catch (Exception e) {
            LogUtils.error(BeanUtil.class, "bean copy 出现异常:", e);
            throw new RuntimeException(e);
        }
        return targetList;
    }


    /**
     * 获取目标对象的  propertyDescriptors 信息
     *
     * @param targetClazz 目标对象的字节码
     * @return 有效 properties描述 list数据
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> targetClazz) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(targetClazz);
            return beanInfo.getPropertyDescriptors();
        } catch (IntrospectionException e) {
            LogUtils.error(BeanUtil.class, "获取目标类字节码的propertiesDescriptors异常", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取某个class的 propertyDescriptor
     *
     * @param clazz        目标class
     * @param propertyName 属性名称
     * @return clazz的propertiesDescriptor
     */
    public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) {
        try {
            PropertyDescriptor propertyDescriptor = cachePropertiesDescriptor.get(clazz.getName() + propertyName);
            if (propertyDescriptor == null) {
                propertyDescriptor = new PropertyDescriptor(propertyName, clazz);
                cachePropertiesDescriptor.put(clazz.getName() + propertyName, propertyDescriptor);
            }
            return propertyDescriptor;
        } catch (IntrospectionException e) {
            LogUtils.debug(BeanUtil.class, String.format("beanUtils copyProperties not found{%s};clazzName{%s}", propertyName, clazz.getName()));
            return null;
        }
    }

    /**
     * 将bean数据转化成map数据
     *
     * @param bean         bean数据
     * @param isIgnoreNull 是否忽略空值
     * @return 真实的map对象
     */
    public static Map<String, String> beanHttpToMap(Object bean, boolean isIgnoreNull) {

        if (bean == null) return null;
        Map<String, String> propertiesMap = new HashMap<>();
        try {
            Class<?> targetClass = bean.getClass();
            BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                String filedName = propertyDescriptor.getName();
                if (filedName.equalsIgnoreCase(defaultIgnoreFiledName)) continue;
                PropertiesName propertiesName = targetClass.getDeclaredField(filedName).getAnnotation(PropertiesName.class);
                filedName = propertiesName != null ? propertiesName.name() : filedName;
                Object result = readMethod.invoke(bean);
                if (isIgnoreNull && Utils.isNull(result)) continue;
                propertiesMap.put(filedName, result == null ? null : result.toString());
            }
            return propertiesMap;
        } catch (Exception e) {
            LogUtils.error(BeanUtil.class, "使用beanUtils将对象转化成map异常", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 将bean数据转化成map数据
     *
     * @param bean         bean数据
     * @param isIgnoreNull 是否忽略空值
     * @return 真实的map对象
     */
    public static Map<String, Object> beanToMap(Object bean, boolean isIgnoreNull) {
        Map<String, Object> propertiesMap = new HashMap<>();
        try {
            Class<?> targetClass = bean.getClass();
            BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                String filedName = propertyDescriptor.getName();
                if (filedName.equalsIgnoreCase(defaultIgnoreFiledName)) continue;
                Field field = getFieldName(filedName, targetClass);
                if (field == null) continue;
                PropertiesName propertiesName = field.getAnnotation(PropertiesName.class);
                filedName = propertiesName != null ? propertiesName.name() : filedName;
                Object result = readMethod.invoke(bean);
                if (isIgnoreNull && Utils.isNull(result)) continue;
                propertiesMap.put(filedName, result == null ? null : result);
            }
            return propertiesMap;
        } catch (Exception e) {
            LogUtils.error(BeanUtil.class, "使用beanUtils将对象转化成map异常", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 将bean数据转化成map数据 并针对 key进行前缀
     *
     * @param bean         bean数据
     * @param isIgnoreNull 是否忽略空值
     * @param keyPrefix    key包装的前缀
     * @param keySuffix    key包装的后缀
     * @return 真实的map对象
     */
    public static Map<String, Object> beanToMapSign(Object bean, boolean isIgnoreNull, String keyPrefix, String keySuffix) {
        Map<String, Object> propertiesMap = new HashMap<>();
        try {
            Class<?> targetClass = bean.getClass();
            BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                String filedName = propertyDescriptor.getName();
                if (filedName.equalsIgnoreCase(defaultIgnoreFiledName)) continue;
                Field field = getFieldName(filedName, targetClass);
                if (field == null) continue;
                PropertiesName propertiesName = field.getAnnotation(PropertiesName.class);
                filedName = propertiesName != null ? propertiesName.name() : filedName;
                Object result = readMethod.invoke(bean);
                if (isIgnoreNull && Utils.isNull(result)) continue;
                propertiesMap.put(keyPrefix + filedName + keySuffix, result == null ? null : result);
            }
            return propertiesMap;
        } catch (Exception e) {
            LogUtils.error(BeanUtil.class, "使用beanUtils将对象转化成map异常", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取真实的字段
     *
     * @param filedName   字段名称
     * @param targetClass 目标类
     * @return 真实字段
     */
    private static Field getFieldName(String filedName, Class<?> targetClass) {
        for (; targetClass != Object.class; targetClass = targetClass.getSuperclass()) {
            try {
                return targetClass.getDeclaredField(filedName);
            } catch (Exception e) {
                //这里甚么都不要做！并且这里的异常必须这样写，不能抛出去。
                //如果这里的异常打印或者往外抛，则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了
            }
        }
        return null;
    }

    /**
     * @description: 拷贝对象
     */
    public static <T> T transforObject(Object o, Class<T> tClass) {
        if (null==o){
            return null;
        }
        try {
            Object to = tClass.newInstance();
            BeanUtil.copyProperties(o, to);
            return (T) to;
        } catch(Exception e) {
            log.error("转换对象有误，{}, -- {}", o.getClass(), tClass);
        }
        return null;
    }
}
